#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <FLAC/stream_encoder.h>

#include "../au.h"
#include "../common.h"
#include "afile.h"

enum {
	A_NULL,
	A_RATE,
	A_CHAN,
	A_ENC
};

struct afile {
	FLAC__StreamEncoder	*enc;
	void			*buf;
	size_t			 icount;
	size_t			 size;
	int			 first;
	int			 fd;
};

static FLAC__StreamEncoderWriteStatus fecb_write(
    const FLAC__StreamEncoder *, const FLAC__byte [], size_t, unsigned,
    unsigned, void *);
static FLAC__StreamEncoderSeekStatus fecb_seek(
    const FLAC__StreamEncoder *, FLAC__uint64, void *);
static FLAC__StreamEncoderTellStatus fecb_tell(
    const FLAC__StreamEncoder *, FLAC__uint64 *, void *);

const struct a_lookup stab[] = {
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ "rate", A_RATE },
	{ NULL, A_NULL }
};

struct afile *
af_open(const char *file) {
	int fd;

	if (file == NULL)
		ERR(EINVAL, NULL);
	if ((fd = open(file, O_WRONLY)) < 0)
		return (NULL);
	return (af_fdopen(fd));
}

struct afile *
af_fdopen(int fd)
{
	struct afile *a;

	if (fd < 0)
		ERR(EBADF, NULL);
	if ((a = malloc(sizeof(struct afile))) == NULL)
		return (NULL);
	a->buf = NULL;
	a->size = 0;
	a->fd = fd;
	a->first = 1;
	if ((a->enc = FLAC__stream_encoder_new()) == NULL) {
		free(a);
		return (NULL);
	}
	return (a);
}

void
af_close(struct afile *a)
{

	if (a == NULL)
		return;
	if (!a->first)
		FLAC__stream_encoder_finish(a->enc);
	FLAC__stream_encoder_delete(a->enc);
	if (a->buf != NULL)
		free(a->buf);
	free(a);
}

ssize_t
af_write(struct afile *a, void *b, size_t size)
{
	size_t i;
	size_t n;
	size_t o;
	size_t t;
	size_t s;
	uint32_t pcm;
	void *p;

	if (a == NULL || b == NULL || a->enc == NULL)
		ERR(EINVAL, -1);
	if (a->first) {
		if (FLAC__stream_encoder_init_stream(a->enc,
		    &fecb_write, &fecb_seek, &fecb_tell, NULL, a) !=
		    FLAC__STREAM_ENCODER_INIT_STATUS_OK)
			ERR(EINVAL, -1);
		a->first = 0;
	}
	o = (FLAC__stream_encoder_get_bits_per_sample(a->enc) + 1) / 8;
	if (size > a->size) {
		s = size / o * sizeof(FLAC__int32);
		if ((p = (a->buf == NULL ? malloc(s) :
		    realloc(a->buf, s))) == NULL)
			return (-1);
		a->buf = p;
		a->size = s;
	}
	s = o * FLAC__stream_encoder_get_channels(a->enc);
	for (i = 0, n = 0; i < size; i += o, n++) {
		for (t = 0, pcm = 0; t < o; t++)
			pcm = (pcm << UINT32_C(8)) |
			    ((uint8_t *)b)[i + t];
		((FLAC__int32 *)a->buf)[n] =
		    (pcm & (UINT32_C(1) << (o * CHAR_BIT - 1))) ?
		    -(FLAC__int32)(~pcm + UINT32_C(1)) : pcm;
		if (o == 4) /* libFLAC requires a maximum of 31 bits */
			((FLAC__int32 *)a->buf)[n] /= 2;
	}
	for (i = 0, size /= s; i < size; i += MIN(UINT_MAX, size - i))
		if (!FLAC__stream_encoder_process_interleaved(a->enc,
		    (void *)((char *)a->buf + i),
		    MIN(UINT_MAX, size - i)))
			return (-1);
	return (size * s);
}

int
af_get(struct afile *a, const char *s, ...)
{
	va_list ap;
	unsigned u;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_RATE:
			*va_arg(ap, uint32_t *) =
			    FLAC__stream_encoder_get_sample_rate
			    (a->enc);
			break;
		case A_CHAN:
			*va_arg(ap, uint32_t *) =
			    FLAC__stream_encoder_get_channels(a->enc);
			break;
		case A_ENC:
			if (a->enc == NULL || (u = 
			    FLAC__stream_encoder_get_bits_per_sample(
			    a->enc)) == 0)
				goto err;
			switch ((u - 1) / 8) {
			case 0:
				*va_arg(ap, uint32_t *) = AU_PCM8;
				break;
			case 1:
				*va_arg(ap, uint32_t *) = AU_PCM16;
				break;
			case 2:
				*va_arg(ap, uint32_t *) = AU_PCM24;
				break;
			case 3:
				*va_arg(ap, uint32_t *) = AU_PCM32;
				break;
			default:
				goto err;
			}
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
af_set(struct afile *a, const char *s, ...)
{
	va_list ap;
	uint32_t enc;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_RATE:
			if (!FLAC__stream_encoder_set_sample_rate(
			    a->enc, va_arg(ap, uint32_t)))
				goto err;
			break;
		case A_CHAN:
			if (!FLAC__stream_encoder_set_channels(a->enc,
			    va_arg(ap, uint32_t)))
				goto err;
			break;
		case A_ENC:
			switch (enc = va_arg(ap, uint32_t)) {
			case AU_PCM8:
				break;
			case AU_PCM16:
				break;
			case AU_PCM24:
				break;
			case AU_PCM32:
				break;
			default:
				goto err;
			}
			if (au_encsize(enc) >= 4 &&
			    !FLAC__stream_encoder_set_bits_per_sample(
			    a->enc, 31))
				goto err;
			if (au_encsize(enc) < 4 &&
			    !FLAC__stream_encoder_set_bits_per_sample(
			    a->enc, au_encsize(enc) * CHAR_BIT))
				goto err;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

FLAC__StreamEncoderWriteStatus
fecb_write(const FLAC__StreamEncoder *enc, const FLAC__byte buf[],
    size_t size, unsigned samples, unsigned frame, void *ctx)
{
	struct afile *a;

	assert(enc != NULL && buf != NULL && ctx != NULL);
	a = ctx;
	if (write(a->fd, buf, size) < 0)
		return (FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR);
	return (FLAC__STREAM_ENCODER_WRITE_STATUS_OK);
}

FLAC__StreamEncoderSeekStatus
fecb_seek(const FLAC__StreamEncoder *enc, FLAC__uint64 v, void *ctx)
{
	struct afile *a;

	assert(enc != NULL && ctx != NULL);
	a = ctx;
	if (lseek(a->fd, v, SEEK_SET) < 0)
		return (errno == ESPIPE ?
		    FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED :
		    FLAC__STREAM_ENCODER_SEEK_STATUS_ERROR);
	return (FLAC__STREAM_ENCODER_SEEK_STATUS_OK);
}

FLAC__StreamEncoderTellStatus
fecb_tell(const FLAC__StreamEncoder *enc, FLAC__uint64 *v, void *ctx)
{
	struct afile *a;
	off_t r;

	assert(enc != NULL && v != NULL && ctx != NULL);
	a = ctx;
	if ((r = lseek(a->fd, 0, SEEK_CUR)) < 0)
		return (errno == ESPIPE ?
		    FLAC__STREAM_ENCODER_TELL_STATUS_UNSUPPORTED :
		    FLAC__STREAM_ENCODER_TELL_STATUS_ERROR);
	if (r > UINT64_MAX)
		ERR(EOVERFLOW, FLAC__STREAM_ENCODER_TELL_STATUS_ERROR);
	*v = r;
	return (FLAC__STREAM_ENCODER_TELL_STATUS_OK);
}
